//     (c) 2012-2016 Airbnb, Inc.
//
//     polyglot.js may be freely distributed under the terms of the BSD
//     license. For all licensing information, details, and documention:
//     http://airbnb.github.com/polyglot.js
//
//
// Polyglot.js is an I18n helper library written in JavaScript, made to
// work both in the browser and in Node. It provides a simple solution for
// interpolation and pluralization, based off of Airbnb's
// experience adding I18n functionality to its Backbone.js and Node apps.
//
// Polylglot is agnostic to your translation backend. It doesn't perform any
// translation; it simply gives you a way to manage translated phrases from
// your client- or server-side JavaScript application.
//


(function(root, factory) {
    if (typeof define === "function" && define.amd) {
        define([], function() {
            return factory(root);
        });
    } else if (typeof exports === "object") {
        module.exports = factory(root);
    } else {
        root.Polyglot = factory(root);
    }
}(typeof global !== "undefined" ? global : this, function(root) {
    "use strict";

    var replace = String.prototype.replace;

  // ### Polyglot class constructor
    function Polyglot(options) {
        options = options || {};
        this.phrases = {};
        this.extend(options.phrases || {});
        this.currentLocale = options.locale || "en";
        this.allowMissing = !!options.allowMissing;
        this.warn = options.warn || warn;
    }

  // ### Version
    Polyglot.VERSION = "1.0.0";

  // ### polyglot.locale([locale])
  //
  // Get or set locale. Internally, Polyglot only uses locale for pluralization.
    Polyglot.prototype.locale = function(newLocale) {
        if (newLocale) this.currentLocale = newLocale;
        return this.currentLocale;
    };

  // ### polyglot.extend(phrases)
  //
  // Use `extend` to tell Polyglot how to translate a given key.
  //
  //     polyglot.extend({
  //       "hello": "Hello",
  //       "hello_name": "Hello, %{name}"
  //     });
  //
  // The key can be any string.  Feel free to call `extend` multiple times;
  // it will override any phrases with the same key, but leave existing phrases
  // untouched.
  //
  // It is also possible to pass nested phrase objects, which get flattened
  // into an object with the nested keys concatenated using dot notation.
  //
  //     polyglot.extend({
  //       "nav": {
  //         "hello": "Hello",
  //         "hello_name": "Hello, %{name}",
  //         "sidebar": {
  //           "welcome": "Welcome"
  //         }
  //       }
  //     });
  //
  //     console.log(polyglot.phrases);
  //     // {
  //     //   'nav.hello': 'Hello',
  //     //   'nav.hello_name': 'Hello, %{name}',
  //     //   'nav.sidebar.welcome': 'Welcome'
  //     // }
  //
  // `extend` accepts an optional second argument, `prefix`, which can be used
  // to prefix every key in the phrases object with some string, using dot
  // notation.
  //
  //     polyglot.extend({
  //       "hello": "Hello",
  //       "hello_name": "Hello, %{name}"
  //     }, "nav");
  //
  //     console.log(polyglot.phrases);
  //     // {
  //     //   'nav.hello': 'Hello',
  //     //   'nav.hello_name': 'Hello, %{name}'
  //     // }
  //
  // This feature is used internally to support nested phrase objects.
    Polyglot.prototype.extend = function(morePhrases, prefix) {
        var phrase;

        for (var key in morePhrases) {
            if (morePhrases.hasOwnProperty(key)) {
                phrase = morePhrases[key];
                if (prefix) key = prefix + "." + key;
                if (typeof phrase === "object") {
                    this.extend(phrase, key);
                } else {
                    this.phrases[key] = phrase;
                }
            }
        }
    };

  // ### polyglot.unset(phrases)
  // Use `unset` to selectively remove keys from a polyglot instance.
  //
  //     polyglot.unset("some_key");
  //     polyglot.unset({
  //       "hello": "Hello",
  //       "hello_name": "Hello, %{name}"
  //     });
  //
  // The unset method can take either a string (for the key), or an object hash with
  // the keys that you would like to unset.
    Polyglot.prototype.unset = function(morePhrases, prefix) {
        var phrase;

        if (typeof morePhrases === "string") {
            delete this.phrases[morePhrases];
        } else {
            for (var key in morePhrases) {
                if (morePhrases.hasOwnProperty(key)) {
                    phrase = morePhrases[key];
                    if (prefix) key = prefix + "." + key;
                    if (typeof phrase === "object") {
                        this.unset(phrase, key);
                    } else {
                        delete this.phrases[key];
                    }
                }
            }
        }
    };

  // ### polyglot.clear()
  //
  // Clears all phrases. Useful for special cases, such as freeing
  // up memory if you have lots of phrases but no longer need to
  // perform any translation. Also used internally by `replace`.
    Polyglot.prototype.clear = function() {
        this.phrases = {};
    };

  // ### polyglot.replace(phrases)
  //
  // Completely replace the existing phrases with a new set of phrases.
  // Normally, just use `extend` to add more phrases, but under certain
  // circumstances, you may want to make sure no old phrases are lying around.
    Polyglot.prototype.replace = function(newPhrases) {
        this.clear();
        this.extend(newPhrases);
    };


  // ### polyglot.t(key, options)
  //
  // The most-used method. Provide a key, and `t` will return the
  // phrase.
  //
  //     polyglot.t("hello");
  //     => "Hello"
  //
  // The phrase value is provided first by a call to `polyglot.extend()` or
  // `polyglot.replace()`.
  //
  // Pass in an object as the second argument to perform interpolation.
  //
  //     polyglot.t("hello_name", {name: "Spike"});
  //     => "Hello, Spike"
  //
  // If you like, you can provide a default value in case the phrase is missing.
  // Use the special option key "_" to specify a default.
  //
  //     polyglot.t("i_like_to_write_in_language", {
  //       _: "I like to write in %{language}.",
  //       language: "JavaScript"
  //     });
  //     => "I like to write in JavaScript."
  //
    Polyglot.prototype.t = function(key, options) {
        var phrase, result;
        options = options == null ? {} : options;
    // allow number as a pluralization shortcut
        if (typeof options === "number") {
            options = {smart_count: options};
        }
        if (typeof this.phrases[key] === "string") {
            phrase = this.phrases[key];
        } else if (typeof options._ === "string") {
            phrase = options._;
        } else if (this.allowMissing) {
            phrase = key;
        } else {
            this.warn("Missing translation for key: \""+key+"\"");
            result = key;
        }
        if (typeof phrase === "string") {
            options = clone(options);
            result = choosePluralForm(phrase, this.currentLocale, options.smart_count);
            result = interpolate(result, options);
        }
        return result;
    };


  // ### polyglot.has(key)
  //
  // Check if polyglot has a translation for given key
    Polyglot.prototype.has = function(key) {
        return key in this.phrases;
    };


  // #### Pluralization methods
  // The string that separates the different phrase possibilities.
    var delimeter = "||||";

  // Mapping from pluralization group plural logic.
    var pluralTypes = {
        chinese:   function(n) { return 0; },
        german:    function(n) { return n !== 1 ? 1 : 0; },
        french:    function(n) { return n > 1 ? 1 : 0; },
        russian:   function(n) { return n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; },
        czech:     function(n) { return (n === 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; },
        polish:    function(n) { return (n === 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); },
        icelandic: function(n) { return (n % 10 !== 1 || n % 100 === 11) ? 1 : 0; }
    };

  // Mapping from pluralization group to individual locales.
    var pluralTypeToLanguages = {
        chinese:   ["fa", "id", "ja", "ko", "lo", "ms", "th", "tr", "zh"],
        german:    ["da", "de", "en", "es", "fi", "el", "he", "hu", "it", "nl", "no", "pt", "sv"],
        french:    ["fr", "tl", "pt-br"],
        russian:   ["hr", "ru"],
        czech:     ["cs", "sk"],
        polish:    ["pl"],
        icelandic: ["is"]
    };

    function langToTypeMap(mapping) {
        var type, langs, l, ret = {};
        for (type in mapping) {
            if (mapping.hasOwnProperty(type)) {
                langs = mapping[type];
                for (l in langs) {
                    ret[langs[l]] = type;
                }
            }
        }
        return ret;
    }

  // Trim a string.
    var trimRe = /^\s+|\s+$/g;
    function trim(str){
        return replace.call(str, trimRe, "");
    }

  // Based on a phrase text that contains `n` plural forms separated
  // by `delimeter`, a `locale`, and a `count`, choose the correct
  // plural form, or none if `count` is `null`.
    function choosePluralForm(text, locale, count){
        var ret, texts, chosenText;
        if (count != null && text) {
            texts = text.split(delimeter);
            chosenText = texts[pluralTypeIndex(locale, count)] || texts[0];
            ret = trim(chosenText);
        } else {
            ret = text;
        }
        return ret;
    }

    function pluralTypeName(locale) {
        var langToPluralType = langToTypeMap(pluralTypeToLanguages);
        return langToPluralType[locale] || langToPluralType.en;
    }

    function pluralTypeIndex(locale, count) {
        return pluralTypes[pluralTypeName(locale)](count);
    }

  // ### interpolate
  //
  // Does the dirty work. Creates a `RegExp` object for each
  // interpolation placeholder.
    var dollarRegex = /\$/g;
    var dollarBillsYall = "$$$$";
    function interpolate(phrase, options) {
        for (var arg in options) {
            if (arg !== "_" && options.hasOwnProperty(arg)) {
        // Ensure replacement value is escaped to prevent special $-prefixed
        // regex replace tokens. the "$$$$" is needed because each "$" needs to
        // be escaped with "$" itself, and we need two in the resulting output.
                var replacement = options[arg];
                if (typeof replacement === "string") {
                    replacement = replace.call(options[arg], dollarRegex, dollarBillsYall);
                }
        // We create a new `RegExp` each time instead of using a more-efficient
        // string replace so that the same argument can be replaced multiple times
        // in the same phrase.
                phrase = replace.call(phrase, new RegExp("%\\{"+arg+"\\}", "g"), replacement);
            }
        }
        return phrase;
    }

  // ### warn
  //
  // Provides a warning in the console if a phrase key is missing.
    function warn(message) {
    // root.console && root.console.warn && root.console.warn('WARNING: ' + message);
    }

  // ### clone
  //
  // Clone an object.
    function clone(source) {
        var ret = {};
        for (var prop in source) {
            ret[prop] = source[prop];
        }
        return ret;
    }

    return Polyglot;
}));
